// Includes
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <limits.h>

#include <iostream>
#include <string>

#include <ctime>
#include <cstdlib>

//Incluyo la clase global.cpp  que contiene las variables y funciones globales
#include "../utils/global.cpp"

//Incluyo la clase mt.cpp para valores aleatorios
#include "../utils/mt.cpp"

using namespace std;

/****************** Representacion binaria ********************/

/******************* Declaracion de Funciones del Host *************************/
void representacionBinaria();

/******************* Declaracion de Funciones Global del  ********************/

 void inicializarPoblacionBinaria(bool* poblacion, int* fitnessActual, MersenneTwister *mt, int cantCromosomasPoblacion);
 void mainKernelBinaria(bool* poblacionActual, bool* nuevaPoblacion, int* fitnessActual, int* nuevoFitness, bool* individuosAMigrar, MersenneTwister *mt, int nroPoblacion, int posActual);
 void migracionRepresentacionBinaria(bool* poblacionActual, int* fitnessActual, bool* individuosAMigrar, MersenneTwister *mt);

/******************* Declaracion de Funciones internas del  *****************/

 void individosAMigrarRepresentacionBinaria(int* nuevoFitness, bool* nuevaPoblacion, bool* individuosAMigrar);

 int seleccionBinaria(int* vectorFitness, MersenneTwister *mt, int ejecutarFuncion, int indiceMejor, int nroPoblacion, int posActual);
 int seleccionPorTorneoBinaria(int* vectorFitness, MersenneTwister *mt, int nroPoblacion, int posActual);
 int seleccionElitistaBinaria(int* vectorFitness, int indiceMejor, int nroPoblacion, int posActual);

 void cruzamientoRepresentacionBinaria(bool* poblacionActual, bool* nuevaPoblacion, int posicionCromosoma1, int posicionCromosoma2, int probabilidad, MersenneTwister *mt, int ejecutarFuncion, int nroPoblacion, int posActual);
 void cruzamientoCromosoma1PuntoBinaria(bool* poblacionActual, bool* nuevaPoblacion, int posicionCromosoma1, int posicionCromosoma2, int probabilidad, MersenneTwister *mt, int nroPoblacion, int posActual);
 void cruzamientoCromosoma2PuntosBinaria(bool* poblacionActual, bool* nuevaPoblacion, int posicionCromosoma1, int posicionCromosoma2, int probabilidad, MersenneTwister *mt, int nroPoblacion, int posActual);
 void cruzamientoCromosomaUniformeBinaria(bool* poblacionActual, bool* nuevaPoblacion, int posicionCromosoma1, int posicionCromosoma2, int probabilidad, MersenneTwister *mt, int nroPoblacion, int posActual);

 void mutacionBinaria(bool* poblacion, int probabilidad, MersenneTwister *mt, int ejecutarFuncion, int nroPoblacion, int posActual);
 void mutacionPorInversion(bool* poblacion, int probabilidad, MersenneTwister *mt, int nroPoblacion, int posActual);

 void asignarFitnessBinaria(bool* nuevaPoblacion, int* nuevoVectorFitness, int nroPoblacion, int posActual);
 int calcularFitnessBinaria(bool* poblacion, int posicion);

/******************* Implementacion de Funciones del  *****************/

 void inicializarPoblacionBinaria(bool* poblacion, int* fitnessActual, MersenneTwister *mt, int cantCromosomasPoblacion){
	//Obtengo posicion a escribir.
	int posicionCromosoma = 0;
	int posicionFitness = 0;

	for(int i = 0; i<cantGenesEnCromosoma * cantidadPoblaciones * cantCromosomasPoblacion; i++){
		//Genero valor aleatorio.
		unsigned int r = (mt->genrand_int32()% 2);
		bool valor = (r == 1);
		poblacion[posicionCromosoma+i] = valor;
	}

	for(int i = 0; i< cantidadPoblaciones * cantCromosomasPoblacion; i++){
		fitnessActual[posicionFitness] = calcularFitnessBinaria(poblacion, posicionCromosoma);
		posicionFitness++;
		posicionCromosoma = posicionCromosoma + cantGenesEnCromosoma;
	}
}

 void mainKernelBinaria(bool* poblacionActual, bool* nuevaPoblacion, int* fitnessActual, int* nuevoFitness, bool* individuosAMigrar, MersenneTwister *mt, int nroPoblacion, int posActual){
	cout<<"** nroPoblacion "<<nroPoblacion<<"\n";
	cout<<"** posActual "<<posActual<<"\n";

	 //genero numero aleatorio entre 0 y cant individuos por poblacion
	int posicionCromosoma1 = seleccionBinaria(fitnessActual, mt, tipoSeleccion, 1, nroPoblacion, posActual);
	int posicionCromosoma2 = seleccionBinaria(fitnessActual, mt, tipoSeleccion, 2, nroPoblacion, posActual);
	//cout<<"posicionCromosoma1 "<<posicionCromosoma1<<"\n";
	//cout<<"posicionCromosoma2 "<<posicionCromosoma2<<"\n";
	cruzamientoRepresentacionBinaria(poblacionActual, nuevaPoblacion, posicionCromosoma1, posicionCromosoma2, probabilidadCruzamiento, mt, tipoCruzamiento, nroPoblacion, posActual);

	mutacionBinaria(nuevaPoblacion, probabilidadMutacion, mt, tipoMutacion, nroPoblacion, posActual);
	asignarFitnessBinaria(nuevaPoblacion, nuevoFitness, nroPoblacion, posActual);

	//__syncthreads();
	//if (threadIdx.x == 0) {
	///	individosAMigrarRepresentacionBinaria(nuevoFitness, nuevaPoblacion, individuosAMigrar);
	//}
}


/******************* Implementacion de Funciones de Migracion *****************/


 void migracionRepresentacionBinaria(bool* poblacionActual, int* fitnessActual, bool* individuosAMigrar, MersenneTwister *mt, int nroPoblacion, int posActual){
	unsigned int r = mt->genrand_int32() % cantCromosomasPoblacion;
	int posicionCromosomaEmigranteAleatoria = nroPoblacion * cantCromosomasPoblacion * cantGenesEnCromosoma + r * cantGenesEnCromosoma;
	int posicionCromosomaInmigrante = ((nroPoblacion + 1) % cantidadPoblaciones )* cantGenesEnCromosoma;
	for (int i = 0; i<cantGenesEnCromosoma; i++) {
		poblacionActual[posicionCromosomaEmigranteAleatoria + i] = individuosAMigrar[posicionCromosomaInmigrante + i];
	}
	int posicionFitness = nroPoblacion * cantCromosomasPoblacion;
	fitnessActual[posicionFitness] = calcularFitnessBinaria(poblacionActual, posicionCromosomaEmigranteAleatoria);

	//printf("posicionCromosomaEmigranteAleatoria = %d \n", posicionCromosomaEmigranteAleatoria);
	//printf("posicionCromosomaInmigrante = %d \n", posicionCromosomaInmigrante);

}

 void individosAMigrarRepresentacionBinaria(int* nuevoFitness, bool* nuevaPoblacion, bool* individuosAMigrar, int nroPoblacion, int posActual){
	int posicionMejorIndividuo = seleccionElitistaBinaria(nuevoFitness, 1, nroPoblacion, posActual);
	for (int i = 0; i<cantGenesEnCromosoma; i++) {
		individuosAMigrar[nroPoblacion*cantGenesEnCromosoma + i] = nuevaPoblacion[posicionMejorIndividuo + i];
	}
}


/******************* Implementacion de Funciones de Selecccion *****************/

 int seleccionBinaria(int* vectorFitness, MersenneTwister *mt, int ejecutarFuncion, int indiceMejor, int nroPoblacion, int posActual){
	int resultado = -1;
	if(ejecutarFuncion == 0) {
		resultado = seleccionElitistaBinaria(vectorFitness, indiceMejor, nroPoblacion, posActual);
	}
	else{
		if(ejecutarFuncion == 1){
			resultado = seleccionPorTorneoBinaria(vectorFitness, mt, nroPoblacion, posActual);
		}
		/*else {
			if(ejecutarFuncion == 2) {
				//seleccionElitistaBinaria(vectorFitness, mt);
			}
		}*/
	}
	return resultado;
}


//Seleccion por torneo
//selecciona 2 individuos al azar y devuelve la posicion del mejor
 int seleccionPorTorneoBinaria(int* vectorFitness, MersenneTwister *mt, int nroPoblacion, int posActual){
	printf("*** Seleccion por torneo ***\n");
	unsigned int r = mt->genrand_int32()% cantCromosomasPoblacion;
	int posicionFitnessAleatoria1 = nroPoblacion * cantCromosomasPoblacion + r;
	int posicionCromosomaAleatoria1 = nroPoblacion * cantCromosomasPoblacion * cantGenesEnCromosoma + r * cantGenesEnCromosoma;
	r = mt->genrand_int32()% cantCromosomasPoblacion;
	int posicionFitnessAleatoria2 = nroPoblacion * cantCromosomasPoblacion + r;
	int posicionCromosomaAleatoria2 = nroPoblacion * cantCromosomasPoblacion * cantGenesEnCromosoma + r * cantGenesEnCromosoma;
	if (vectorFitness[posicionFitnessAleatoria1] > vectorFitness[posicionFitnessAleatoria2])
		return posicionCromosomaAleatoria1;
	return posicionCromosomaAleatoria2;
}

//Seleccion elitista
//selecciona el mejor o segundo mejor elemento segun se indica en indiceMejor con un 1 o con un 2
 int seleccionElitistaBinaria(int* vectorFitness, int indiceMejor, int nroPoblacion, int posActual){
	printf("*** Seleccion elitista ***\n");
	int mejor1 = -1;
	int mejor2 = -2;
	int indiceMejor1, indiceMejor2;
	int valorFitness;
	int indiceMemoria = nroPoblacion * cantCromosomasPoblacion;
	for(int i = 0; i< cantCromosomasPoblacion; i++){
		valorFitness = vectorFitness[indiceMemoria + i];
		if (valorFitness > mejor1) {
			mejor2 = mejor1;
			indiceMejor2 = indiceMejor1;
			mejor1 = valorFitness;
			indiceMejor1 = i;
		}
		else {
			if (valorFitness > mejor2){
				mejor2 = valorFitness;
				indiceMejor2 = i;
			}
		}
	}
	int posicionCromosomaMejor1 = nroPoblacion * cantCromosomasPoblacion * cantGenesEnCromosoma + indiceMejor1 * cantGenesEnCromosoma;
	int posicionCromosomaMejor2 = nroPoblacion * cantCromosomasPoblacion * cantGenesEnCromosoma + indiceMejor2 * cantGenesEnCromosoma;
	if (indiceMejor == 1) {
		return posicionCromosomaMejor1;
	}
	return posicionCromosomaMejor2;
}

/******************* Implementacion de Funciones de Cruzamiento *****************/

 void cruzamientoRepresentacionBinaria(bool* poblacionActual, bool* nuevaPoblacion, int posicionCromosoma1, int posicionCromosoma2, int probabilidad, MersenneTwister *mt, int ejecutarFuncion, int nroPoblacion, int posActual){

	if(ejecutarFuncion == 0) {
		cruzamientoCromosoma1PuntoBinaria(poblacionActual, nuevaPoblacion, posicionCromosoma1, posicionCromosoma2, probabilidad, mt, nroPoblacion, posActual);
	}
	else{
		if(ejecutarFuncion == 1){
			cruzamientoCromosoma2PuntosBinaria(poblacionActual, nuevaPoblacion, posicionCromosoma1, posicionCromosoma2, probabilidad, mt, nroPoblacion, posActual);
		}
		else {
			if(ejecutarFuncion == 2) {
				cruzamientoCromosomaUniformeBinaria(poblacionActual, nuevaPoblacion, posicionCromosoma1, posicionCromosoma2, probabilidad, mt, nroPoblacion, posActual);
			}
		}
	}
}

//Se selecciona un punto de corte y se intercambian los genes de los padres para formar los hijos
 void cruzamientoCromosoma1PuntoBinaria(bool* poblacionActual, bool* nuevaPoblacion, int posicionCromosoma1, int posicionCromosoma2, int probabilidad, MersenneTwister *mt, int nroPoblacion, int posActual){
	printf("*** Cruzamiento 1Punto ***\n");
	int posicionNuevoCromosoma1 = posActual * cantGenesEnCromosoma * 2 + nroPoblacion * totalGenesEnPoblacion;
	int posicionNuevoCromosoma2 = posicionNuevoCromosoma1 + cantGenesEnCromosoma;
	//printf("posicionCromosoma1  %d\n", posicionCromosoma1);
	//printf("posicionCromosoma2  %d\n", posicionCromosoma2);

	unsigned int prob = (mt->genrand_int32() % 101);
	if (prob < probabilidad){
		unsigned int r = mt->genrand_int32() % cantGenesEnCromosoma;
		printf("r %d\n", r);
		for(int i=0; i<cantGenesEnCromosoma; i++){
			if(i<=r){
				nuevaPoblacion[posicionNuevoCromosoma1+i] = poblacionActual[posicionCromosoma1 + i];
				nuevaPoblacion[posicionNuevoCromosoma2+i] = poblacionActual[posicionCromosoma2 + i];
			}else{
				nuevaPoblacion[posicionNuevoCromosoma1+i] = poblacionActual[posicionCromosoma2 + i];
				nuevaPoblacion[posicionNuevoCromosoma2+i] = poblacionActual[posicionCromosoma1 + i];
			}
		}
	}else{
		for(int i=0; i<cantGenesEnCromosoma; i++){
			nuevaPoblacion[posicionNuevoCromosoma1+i] = poblacionActual[posicionCromosoma1 + i];
			nuevaPoblacion[posicionNuevoCromosoma2+i] = poblacionActual[posicionCromosoma2 + i];
		}
	}
}

//Se seleccionan 2 puntos de corte y se intercambian los genes de los padres para formar los hijos
 void cruzamientoCromosoma2PuntosBinaria(bool* poblacionActual, bool* nuevaPoblacion, int posicionCromosoma1, int posicionCromosoma2, int probabilidad, MersenneTwister *mt, int nroPoblacion, int posActual){
	printf("*** Cruzamiento 2Puntos ***\n");
	int posicionNuevoCromosoma1 = posActual * cantGenesEnCromosoma * 2 + nroPoblacion * totalGenesEnPoblacion;
	int posicionNuevoCromosoma2 = posicionNuevoCromosoma1 + cantGenesEnCromosoma;
	unsigned int prob = (mt->genrand_int32() % 101);
	if (prob < probabilidad){
		unsigned int r1 = mt->genrand_int32() % cantGenesEnCromosoma;
		unsigned int r2 = mt->genrand_int32() % cantGenesEnCromosoma;
		printf("r1 %d\n", r1);
		printf("r2 %d\n", r2);

		int i = 0;
		while(r2 == r1){
			r2 = (mt->genrand_int32() + 1) % cantGenesEnCromosoma;
			i++;
		}

		if(r2<r1){
			unsigned int r3 = r1;
			r1 = r2;
			r2 = r3;
		}

		for(int i=0; i<cantGenesEnCromosoma; i++){
			if(i<r1){
				nuevaPoblacion[posicionNuevoCromosoma1+i] = poblacionActual[posicionCromosoma1 + i];
				nuevaPoblacion[posicionNuevoCromosoma2+i] = poblacionActual[posicionCromosoma2 + i];
			}else if(i<=r2){
				nuevaPoblacion[posicionNuevoCromosoma1+i] = poblacionActual[posicionCromosoma2 + i];
				nuevaPoblacion[posicionNuevoCromosoma2+i] = poblacionActual[posicionCromosoma1 + i];
			}else{
				nuevaPoblacion[posicionNuevoCromosoma1+i] = poblacionActual[posicionCromosoma1 + i];
				nuevaPoblacion[posicionNuevoCromosoma2+i] = poblacionActual[posicionCromosoma2 + i];
			}
		}
	}else{
		for(int i=0; i<cantGenesEnCromosoma; i++){
			nuevaPoblacion[posicionNuevoCromosoma1+i] = poblacionActual[posicionCromosoma1 + i];
			nuevaPoblacion[posicionNuevoCromosoma2+i] = poblacionActual[posicionCromosoma2 + i];
		}
	}
}


//en cada posición de los progenitores se decide intercambiar el valor
//genético de cada punto de acuerdo a una probabilidad prefijada
 void cruzamientoCromosomaUniformeBinaria(bool* poblacionActual, bool* nuevaPoblacion, int posicionCromosoma1, int posicionCromosoma2, int probabilidad, MersenneTwister *mt, int nroPoblacion, int posActual){
	printf("*** Cruzamiento Uniforme ***\n");
	int posicionNuevoCromosoma1 = posActual * cantGenesEnCromosoma * 2 + nroPoblacion * totalGenesEnPoblacion;
	int posicionNuevoCromosoma2 = posicionNuevoCromosoma1 + cantGenesEnCromosoma;
	printf("posicionNuevoCromosoma1  %d\n", posicionNuevoCromosoma1);
	printf("posicionNuevoCromosoma2  %d\n", posicionNuevoCromosoma2);

	unsigned int prob;
	for(int i=0; i<cantGenesEnCromosoma; i++){
		prob = (mt->genrand_int32() % 101);
		if(prob < probabilidad ){
			printf("i = %d", i);
			nuevaPoblacion[posicionNuevoCromosoma1+i] = poblacionActual[posicionCromosoma2 + i];
			nuevaPoblacion[posicionNuevoCromosoma2+i] = poblacionActual[posicionCromosoma1 + i];
		}
		else {
			nuevaPoblacion[posicionNuevoCromosoma1+i] = poblacionActual[posicionCromosoma1 + i];
			nuevaPoblacion[posicionNuevoCromosoma2+i] = poblacionActual[posicionCromosoma2 + i];
		}
	}
}


/******************* Implementacion de Funciones de Mutacion *****************/

 void mutacionBinaria(bool* poblacion, int probabilidad, MersenneTwister *mt, int ejecutarFuncion, int nroPoblacion, int posActual){
	if(ejecutarFuncion == 0) {
		mutacionPorInversion(poblacion, probabilidad, mt, nroPoblacion, posActual);
	}
}

//Mutacion por inversion del valor del alelo
//Se modifica aleatoriamente (con probabilidad ''probabilidad') uno de los valores binarios del cromosoma
 void mutacionPorInversion(bool* poblacion, int probabilidad, MersenneTwister *mt, int nroPoblacion, int posActual){
	printf("*** Mutacion por inversion ***\n");
	unsigned int r;
	int posicionNuevoCromosoma1 = posActual * cantGenesEnCromosoma * 2 + nroPoblacion * cantCromosomasPoblacion * cantGenesEnCromosoma;
	int posicionNuevoCromosoma2 = posicionNuevoCromosoma1 + cantGenesEnCromosoma;
	if((mt->genrand_int32() % 101) >= probabilidad ){
		r = (mt->genrand_int32() % cantGenesEnCromosoma);
		printf("r  %d\n", r);
		if(poblacion[posicionNuevoCromosoma1+r] == 0)
			poblacion[posicionNuevoCromosoma1+r] = 1;
		else
			poblacion[posicionNuevoCromosoma1+r] = 0;
	}
	if((mt->genrand_int32() % 101) >= probabilidad ){
		r = (mt->genrand_int32() % cantGenesEnCromosoma);
		printf("r  %d\n", r);
		if(poblacion[posicionNuevoCromosoma2+r] == 0)
			poblacion[posicionNuevoCromosoma2+r] = 1;
		else
			poblacion[posicionNuevoCromosoma2+r] = 0;
	}
}



/******************* Implementacion de Funciones de fitness *****************/

void asignarFitnessBinaria(bool* nuevaPoblacion, int* nuevoVectorFitness, int nroPoblacion, int posActual){
 	//Obtengo posicion del primer cromosoma.
 	int posicionCromosoma = posActual * cantGenesEnCromosoma * 2 + nroPoblacion * totalGenesEnPoblacion;
 	int posicionFitness = posActual * 2 + nroPoblacion * cantCromosomasPoblacion;
 	nuevoVectorFitness[posicionFitness] = calcularFitnessBinaria(nuevaPoblacion, posicionCromosoma);
 	nuevoVectorFitness[posicionFitness + 1] = calcularFitnessBinaria(nuevaPoblacion, posicionCromosoma + cantGenesEnCromosoma);
 }

 int calcularFitnessBinaria(bool* poblacion, int posicion) {
	int fitness = 0;
	float contador = cantGenesEnCromosoma-1;
	for(int i=0; i<cantGenesEnCromosoma; i++){
	  fitness = fitness + poblacion[posicion + i] * pow(2,contador);
	  contador--;
	}
	return fitness;
}

/******************* Implementacion de Funciones del host *****************/

void representacionBinaria() {

	int tamVectorPoblacion = sizeof(int)*totalGenes;
	int tamVectorFitness = sizeof(int)*totalIndividuos;
	int tamVectorIndividuosAMigrar = sizeof(int)*cantGenesEnCromosoma*cantidadPoblaciones;

	static bool *poblacionActualHost = (bool *) malloc(tamVectorPoblacion);
	static bool *nuevaPoblacionHost = (bool *) malloc(tamVectorPoblacion);
	static int *fitnessActualHost = (int *) malloc(tamVectorFitness);
	static int *nuevoFitnessHost = (int *) malloc(tamVectorFitness);
	static bool *individuosAMigrarHost = (bool *) malloc(tamVectorIndividuosAMigrar);

	//inicializo la clase mt para las variables aleatorias
	MersenneTwister* mt;

	//Inicio numeros aleatorios.
	mt = new MersenneTwister();
	mt->init_genrand(time(NULL));

	//inicializacion de la poblacion en GPU
	inicializarPoblacionBinaria(poblacionActualHost, fitnessActualHost, mt, cantCromosomasPoblacion);

	cout << "\nPOBLACION INICIAL\n";
	for (int i = 0; i<totalGenes; i++){
		if(i%cantGenesEnCromosoma == 0){
			cout << "\n";

		}
		printf("%i ", poblacionActualHost[i]);
	}
	cout << "\n\n\nFITNESS\n";

	for (int i = 0; i<cantCromosomasPoblacion * cantidadPoblaciones; i++){
		printf("%i ", fitnessActualHost[i]);
	}
	cout << "\n\n\n\n";

	int nroPoblacion;
	int iteracion = 0;
	//los individuos de la poblacion actual se van intercambiando en el reemplazo
	while(iteracion < numeroGeneraciones){
		nroPoblacion = 0;
		for (int i = 0; i < cantidadPoblaciones*cantCromosomasPoblacion/2; i++){
			nroPoblacion = i/(cantCromosomasPoblacion/2);
			if ((iteracion % 2) == 0) {

				mainKernelBinaria(poblacionActualHost, nuevaPoblacionHost, fitnessActualHost, nuevoFitnessHost, individuosAMigrarHost, mt, nroPoblacion, i%(cantCromosomasPoblacion/2));

				//Si hay que migrar
				//if (iteracion % generacionesMigrar == 0) {
				//	migracionRepresentacionBinaria(nuevaPoblacionHost, nuevoFitnessHost, individuosAMigrarHost, r);
				//}
			}
			else {
				mainKernelBinaria(nuevaPoblacionHost, poblacionActualHost, nuevoFitnessHost, fitnessActualHost, individuosAMigrarHost, mt, nroPoblacion,  i%(cantCromosomasPoblacion/2));

				//Si hay que migrar
				//if (iteracion % generacionesMigrar == 0) {
				//	migracionRepresentacionBinaria(poblacionActualHost, fitnessActualHost, individuosAMigrarHost, r);
				//}
			}

		}
		//mejorFitness = obtenerMejorFitness(poblacionActual,mejorCromosoma);
		iteracion++;
	}


	cout << "NUEVA POBLACION\n";
	for (int i = 0; i<totalGenes; i++){
		if(i%cantGenesEnCromosoma == 0){
			cout << "\n";

		}
		printf("%i ", nuevaPoblacionHost[i]);
	}
	cout << "\n\n\nFITNESS\n";

	for (int i = 0; i<cantCromosomasPoblacion * cantidadPoblaciones; i++){
		printf("%i ", nuevoFitnessHost[i]);
	}
	cout << "\n";

/*	cout << "\n\nA MIGRAR\n";

	for (int i = 0; i<cantGenesEnCromosoma * cantidadPoblaciones; i++){
		if(i%cantGenesEnCromosoma == 0){
			cout << "\n";
		}
		printf("%i ", individuosAMigrarHost[i]);
	}
	cout << "\n";
*/
}
